/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { toHttpDateOrNull } from './utils/StringUtil'
import { List } from '@kit.ArkTS';
import { kotlinRequire } from './utils/Utils';

export class Headers {
  private namesAndValues: string[];
  
  constructor(namesAndValues: List<string>) {
    this.namesAndValues = namesAndValues.convertToArray()
  }

  /** Returns the last value corresponding to the specified field, or null. */
  // operator fun get(name: String): String? = get(namesAndValues, name)
  public get(name: string): string | null {
    for (let i = this.namesAndValues.length - 2;i >= 0; i -= 2) {
      if (name.toLowerCase() == this.namesAndValues[i].toLowerCase()) {
        return this.namesAndValues[i + 1];
      }
    }
    return null
  }

  /**
   * Returns the last value corresponding to the specified field parsed as an HTTP date, or null if
   * either the field is absent or cannot be parsed as a date.
   */
  public getDate(name: string): Date | null {

    let value = this.get(name)
    if (value !== null && value !== undefined) {
      return toHttpDateOrNull(value)
    }
    return null;
  }

  /**
   * Returns the last value corresponding to the specified field parsed as an HTTP date, or null if
   * either the field is absent or cannot be parsed as a date.
   */
  getInstant(name: string){
    return this.getDate(name)
  }

  get size(): number {
    return this.namesAndValues.length / 2;
  }
  /** Returns the field at `position`. */
  name(index: number): string {
    return this.namesAndValues[index * 2];
  }

  /** Returns the value at `index`. */
  value(index: number): string {
    return this.namesAndValues[index * 2 + 1];
  }

  /** Returns an immutable case-insensitive set of header names. */
  public names(): ReadonlySet<string> {
    let result = new Set<string>();
    // Use lowercase to simulate case-insensitive behavior
    let seen = new Set<string>();
    for (let i = 0; i < this.size; i++) {
      let lowerName = this.name(i).toLowerCase();
      if (!seen.has(lowerName)) {
        seen.add(lowerName);
        result.add(this.name(i)); // Keep original case
      }
    }
    return result
  }
  /** Returns an immutable list of the header values for `name`. */
  public values(name: string): Readonly<string[]> {
    let result:string[] = [];
    for (var i = 0; i < this.size; i++) {
      if (name.toLowerCase() == this.name(i).toLowerCase()) {
        result.push(this.value(i))
      }
    }
    return result
  }

  /**
   * Returns the number of bytes required to encode these headers using HTTP/1.1. This is also the
   * approximate size of HTTP/2 headers before they are compressed with HPACK. This value is
   * intended to be used as a metric: smaller headers are more efficient to encode and transmit.
   * 
   * Each header name has 2 bytes of overhead for ': ' and every header value has 2 bytes of
   * overhead for '\r\n'.
   */
  public byteCount(): number {
    let result = this.namesAndValues.length * 2;
    for (let i = 0; i < this.namesAndValues.length; i++) {
      result += this.namesAndValues[i].length;
    }
    return result;
  }

  // kotlin迭代器,暂不需要
  // override operator fun iterator(): Iterator<Pair<String, String>> {
  //     return Array(size) { name(it) to value(it) }.iterator()
  // }

  public newBuilder() {
    let result = new Headers.Builder();
    for (let i = 0; i < this.namesAndValues.length; i++) {
      result.namesAndValues.add(this.namesAndValues[i])
    }
    return result
  }

  equals(other: unknown): boolean{
    return other instanceof Headers && this.namesAndValues.length === other.namesAndValues.length &&
    this.namesAndValues.every((value, index) => value === other.namesAndValues[index])
  }

  // 暂不实现
  // override fun hashCode(): Int = namesAndValues.contentHashCode()

  /**
   * Returns header names and values. The names and values are separated by `: ` and each pair is
   * followed by a newline character `\n`.
   *
   * Since OkHttp 5 this redacts these sensitive headers:
   *
   *  * `Authorization`
   *  * `Cookie`
   *  * `Proxy-Authorization`
   *  * `Set-Cookie`
   *
   * To get all headers as a human-readable string use `toMultimap().toString()`.
   */
  toString(): string {
    let result = "";
    for (let i = 0; i < this.size; i++) {
      let name = this.name(i);
      let value = this.value(i);
      result += name + ": ";
      result += Headers.isSensitiveHeader(name) ? "██" : value;
      result += "\n";
    }
    return result
  }

  toMultimap(): Map<string, Array<string>> {
    let result = new Map<string, Array<string>>();
    for (var index = 0; index < this.size; index++) {
      let name = this.name(index).toLowerCase()
      let values = result.get(name)
      if (values === undefined) {
        values = new Array<string>()
        result.set(name, values)
      }
      values.push(this.value(index));
    }
    return result
  }


  static get Builder() {
    class Builder {
      namesAndValues: List<string> = new List<string>()

      /**
       * Add a header line without any validation. Only appropriate for headers from the remote peer
       * or cache.
       */
      addLenientWithLine(line: string) {
        let index = line.indexOf(":", 1)
        if (index !== -1) {
          this.addLenient(line.substring(0, index), line.substring(index + 1));
        }else if (line !== "" && line[0] == ':'){
          // Work around empty header names and header names that start with a colon (created by old
          // broken SPDY versions of the response cache).
          this.addLenient("", line.substring(1));
        }else{
          this.addLenient("", line);
        }

      }

      /** Add an header line containing a field name, a literal colon, and a value. */
      // fun add(line: String) = apply {
      //     val index = line.indexOf(':')
      //     require(index != -1) { "Unexpected header: $line" }
      // add(line.substring(0, index).trim(), line.substring(index + 1))
      // }

      addWithLine(line: string): Builder{
        let index = line.indexOf(':')
        kotlinRequire(index !== -1, `Unexpected header: ${line}`)
        this.add(line.substring(0, index).trim(), line.substring(index + 1))
        return this
      }

      /**
       * Add a header with the specified name and value. Does validation of header names and values.
       */
      add(name: string, value: string): Builder{
        Headers.checkName(name)
        Headers.checkValue(value, name)
        this.addLenient(name, value)
        return this
      }

      /**
       * Add a header with the specified name and value. Does validation of header names, allowing
       * non-ASCII values.
       */
      addUnsafeNonAscii(name: string, value: string): Builder{
        Headers.checkName(name)
        this.addLenient(name, value)
        return this
      }

      /**
       * Adds all headers from an existing collection.
       */
      addAll(headers: Headers): Builder{
        for (let i = 0; i < headers.size; i++) {
          this.add(headers.name(i), headers.value(i))
        }
        return this
      }

      /**
       * Add a header with the specified name and formatted date. Does validation of header names and
       * value.
       */
      addWithDate(name: string, value: Date): Builder{
        this.add(name, value.toString())
        return this
      }

      // 无需实现 kotlin强相关
      /**
       * Add a header with the specified name and formatted instant. Does validation of header names
       * and value.
       */
      // @IgnoreJRERequirement
      // fun add(name: String, value: Instant) = apply {
      //     add(name, Date(value.toEpochMilli()))
      // }

      /**
       * Set a field with the specified date. If the field is not found, it is added. If the field is
       * found, the existing values are replaced.
       */
      setWithDate(name: string, value: Date): Builder{
        this.set(name, value.toString())
        return this
      }

      // 无需实现 kotlin强相关
      /**
       * Set a field with the specified instant. If the field is not found, it is added. If the field
       * is found, the existing values are replaced.
       */
      // @IgnoreJRERequirement
      // operator fun set(name: String, value: Instant) = apply {
      //     return set(name, Date(value.toEpochMilli()))
      // }

      /**
       * Add a field with the specified value without any validation. Only appropriate for headers
       * from the remote peer or cache.
       */
      addLenient(name: string, value: string): Builder {
        this.namesAndValues.add(name)
        this.namesAndValues.add(value.trim())
        return this;
      }

      removeAll(name: string): Builder{
        let i = 0
        while (i < this.namesAndValues.length) {
          if (name.toLowerCase() === this.namesAndValues[i].toLowerCase()) {
            this.namesAndValues.removeByIndex(i) // name
            this.namesAndValues.removeByIndex(i) // value
            i -= 2
          }
          i += 2
        }
        return this;
      }

      /**
       * Set a field with the specified value. If the field is not found, it is added. If the field is
       * found, the existing values are replaced.
       */
      set(name: string, value: string): Builder {
        Headers.checkName(name)
        Headers.checkValue(value, name)
        this.removeAll(name)
        this.addLenient(name, value)
        return this
      }

      /** Equivalent to `build().get(name)`, but potentially faster. */
      get(name: string): string | null {
        for (let i = this.namesAndValues.length - 2; i >= 0; i -= 2) {
          if (name.toLowerCase() === this.namesAndValues.get(i).toLowerCase()) {
            return this.namesAndValues[i + 1]
          }
        }
        return null
      }

      build(): Headers{
        if (this.namesAndValues.length == 0) {
          return new Headers(this.namesAndValues)
        }
        return new Headers(this.namesAndValues)
      }

    }
    return Builder
  }

  /**
   * Returns headers for the alternating header names and values. There must be an even number of
   * arguments, and they must alternate between header names and values.
   */
  static headersOf(...namesAndValues: string[]): Headers {
    kotlinRequire(namesAndValues.length % 2 == 0, "Expected alternating header names and values")
    // Make a defensive copy and clean it up
    const cleaned: string[] = [];
    for (let i = 0; i < namesAndValues.length; i++) {
      kotlinRequire(namesAndValues[i] !== null, "Headers cannot be null")
      cleaned.push(namesAndValues[i].trim());
    }

    // Check for malformed headers
    for (let i = 0; i < cleaned.length; i += 2) {
      const name = cleaned[i];
      const value = cleaned[i + 1];
      Headers.checkName(name);
      Headers.checkValue(value, name);
    }

    const list = new List<string>();
    for (const item of cleaned) {
      list.add(item);
    }
    return new Headers(list);
  }

  /**
   * Returns headers for the header names and values in the Map.
   * Creates a defensive copy and validates all header names and values.
   */
  static fromMap(headers: Map<string, string>): Headers {
    // Make a defensive copy and clean it up
    const namesAndValues: string[] = [];
    
    for (const [k, v] of headers) {
      const name = k.trim();
      const value = v.trim();
      Headers.checkName(name);
      Headers.checkValue(value, name);
      namesAndValues.push(name);
      namesAndValues.push(value);
    }

    const list = new List<string>();
    for (const item of namesAndValues) {
      list.add(item);
    }
    return new Headers(list);
  }

  /**
   * Returns headers for the header names and values in the object.
   * This is a convenience method for JavaScript-style objects.
   */
  static fromObject(headers: Record<string, string>): Headers {
    const map = new Map<string, string>();
    for (const [key, value] of Object.entries(headers)) {
      map.set(key, value);
    }
    return Headers.fromMap(map);
  }

  static checkName(name: string): void {
    kotlinRequire(name !== null && name !== '', "name is empty")

    for (let i = 0; i < name.length; i++) {
      let c = name.charAt(i);
      let code = c.charCodeAt(0);
      // 检查是否在合法范围内：\u0021 ~ \u007E (33 ~ 126)
      kotlinRequire(code >= 0x21 && code <= 0x7E,
        `Unexpected char %${code.toString(16).padStart(4, '0')} at ${i} in header name: ${name}`)
    }
  }

  static checkValue(value: string, name: string) {
    for (let i = 0; i < value.length; i++) {
      let c = value.charAt(i);
      let code = c.charCodeAt(0);
      kotlinRequire(c === '\t' || (c >= '\u0020' && c <= '\u007e'),
        `Unexpected char %${code.toString(16).padStart(4, '0')} at ${i} in ${name} value` + !Headers.isSensitiveHeader(name) ? `: ${value}` : '')
    }
  }

  // 实现可迭代协议
  [Symbol.iterator](): Iterator<[string, string]> {
    const size = this.size;
    const entries: Array<[string, string]> = [];

    for (let i = 0; i < size; i++) {
      entries.push([this.name(i), this.value(i)]);
    }

    return entries[Symbol.iterator]();
  }

  /**
   * Returns true if the header name is sensitive and should be redacted in logs.
   * Sensitive headers: Authorization, Cookie, Proxy-Authorization, Set-Cookie
   */
  private static isSensitiveHeader(name: string): boolean {
    const lowerName = name.toLowerCase();
    return lowerName === 'authorization' ||
           lowerName === 'cookie' ||
           lowerName === 'proxy-authorization' ||
           lowerName === 'set-cookie';
  }
}